/*
===========================================================================
This source file is part of DyLab (Dynamic Laboratory)
For the latest info, see http://dylab.googlecode.com

Copyright (c) 2006-2008 Lukas Krejci
(krejci.lukas@volny.cz)

This file is part of DyLab.

    DyLab is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    DyLab is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with DyLab.  If not, see <http://www.gnu.org/licenses/>.
===========================================================================
*/

//===========================================================================================
#include <dylab/utils/topology/SimplicalComplex.h>
#include <dylab/utils/exceptions/EInvalidArgument.h>

//===========================================================================================
// Simplex
//===========================================================================================
dylab::SimplicalComplex::Simplex::Simplex(order_t _order, id_t _id, SimplicalComplex * _complex, bool _removeSubBoundaries, bool _notifyComplex)
: mOrder(_order), mId(_id), mParentComplex(_complex), mRemoveSubBoundaries(_removeSubBoundaries)
{     
    // check the _complex pointer
    DYLAB_ASSERT(mParentComplex != NULL);    

    // notify _complex that this simplex has been created
    if (_notifyComplex)
        mParentComplex->_notifySimplexCreated(this);
}
//-----------------------------------------------------------------------------
dylab::SimplicalComplex::Simplex::~Simplex()
{

    // walk through all sub-simplices
    for (OtherSimplices::Iterator subsimplex(mSubSimplices); subsimplex.isValid(); subsimplex++)
    {
        // detach the subsimplex
        subsimplex->_detachSimplex(this);

        // remove the sub-_simplex if we should remove all sub-boundaries and it is a boundary
        if (mRemoveSubBoundaries && subsimplex->isBoundary())            
            mParentComplex->destroySimplex(subsimplex->getOrder(), ~subsimplex);
    }

    // detach from all super-simplices
    for (OtherSimplices::Iterator supersimplex(mSuperSimplices); supersimplex.isValid(); supersimplex++)
        supersimplex->_detachSimplex(this);
}   
//-----------------------------------------------------------------------------
void dylab::SimplicalComplex::Simplex::connect(Simplex * _simplex)
{
    //
    DYLAB_ASSERT(_simplex != NULL);

    //
    _simplex->_attachSimplex(this);

    //
    _attachSimplex(_simplex);
}
//-----------------------------------------------------------------------------
void dylab::SimplicalComplex::Simplex::disconnect(Simplex * _simplex)
{ 
    //
    DYLAB_ASSERT(_simplex != NULL);

    //
    _simplex->_detachSimplex(this);

    //
    _detachSimplex(_simplex);
}
//-----------------------------------------------------------------------------
void dylab::SimplicalComplex::Simplex::_attachSimplex(Simplex * _simplex)
{
    //
    DYLAB_ASSERT(_simplex != NULL);

    //
    OtherSimplices * simplices;
    if (isSubSimplex(_simplex))
        simplices = &mSubSimplices;
    else if (isSuperSimplex(_simplex))
        simplices = &mSuperSimplices;
    else 
        throw EInvalidArgument("Can't connect simplex that is not sub- neither super-simplex.");

    //
    DYLAB_ASSERT(!simplices->has(_simplex->getId()));
    simplices->insert(_simplex->getId(), _simplex); 
}
//-----------------------------------------------------------------------------
void dylab::SimplicalComplex::Simplex::_detachSimplex(Simplex * _simplex)
{ 
    //
    DYLAB_ASSERT(_simplex != NULL);
    
    //
    OtherSimplices * simplices;
    if (isSubSimplex(_simplex))
        simplices = &mSubSimplices;
    else if (isSuperSimplex(_simplex))
        simplices = &mSuperSimplices;
    else 
        throw EInvalidArgument("Can't connect simplex that is not sub- neither super-simplex.");

    //
    simplices->remove(_simplex->getId()); 
}


//===========================================================================================
// AdjacentSimplices
//===========================================================================================
dylab::SimplicalComplex::AdjacentSimplices::AdjacentSimplices(Simplex::order_t _order)
: mOrder(_order)
{ 
}
//-----------------------------------------------------------------------------
dylab::SimplicalComplex::AdjacentSimplices::~AdjacentSimplices()
{ 
}
//-----------------------------------------------------------------------------
void dylab::SimplicalComplex::AdjacentSimplices::mergeSimplex(Simplex * _simplex)
{
    //
    DYLAB_ASSERT(_simplex != NULL);
    DYLAB_ASSERT(_simplex->getOrder() == mOrder);

    // if not in the map yet
    Simplex::id_t _simplexId = _simplex->getId();
    if (!mSimplices.has(_simplexId))
        mSimplices.insert(_simplexId, _simplex);
}
//-----------------------------------------------------------------------------
void dylab::SimplicalComplex::AdjacentSimplices::mergeSimplices(const Simplices & _simplices, Simplex::id_t _excludeId)
{
    // walk through all given simplices
    for (Simplices::ConstIterator simplex(_simplices); simplex.isValid(); simplex++)
    {
        // if not equal to excluded _simplex
        if (simplex->getId() != _excludeId)
            mergeSimplex(simplex());
    }
}
//-----------------------------------------------------------------------------
void dylab::SimplicalComplex::AdjacentSimplices::mergeSubAdjacentSimplices(Simplex * _sourceSimplex)
{ 
    // check the argument
    DYLAB_ASSERT(_sourceSimplex != NULL);
    DYLAB_ASSERT(_sourceSimplex->getOrder() == mOrder);

    // check the _order of the source _simplex
    if (_sourceSimplex->getOrder() == Simplex::ORDER_0)
        throw EInvalidArgument(String::fmt("Can't merge sub-adjacent simplex map from 0-simplex (id:%1%).", 
			_sourceSimplex->getId()));

    // walk through all sub-simplices
    for (Simplices::Iterator subSimplex(_sourceSimplex->getSubSimplices()); subSimplex.isValid(); subSimplex++)
        mergeSimplices(subSimplex->getSuperSimplices(), _sourceSimplex->getId());
}
//-----------------------------------------------------------------------------
void dylab::SimplicalComplex::AdjacentSimplices::mergeSuperAdjacentSimplices(Simplex * _sourceSimplex)
{
    // check the argument
    DYLAB_ASSERT(_sourceSimplex != NULL);
    DYLAB_ASSERT(_sourceSimplex->getOrder() == mOrder);

    // check the _order of the source _simplex
    if (_sourceSimplex->getOrder() == Simplex::MAX_ORDER)
        throw EInvalidArgument(String::fmt("Can't merge super-adjacent simplex map from 3-simplex (id:%1%).", 
			_sourceSimplex->getId()));

    // walk through all super-simplices
    for (Simplices::Iterator superSimplex(_sourceSimplex->getSuperSimplices()); superSimplex.isValid(); superSimplex++)
        mergeSimplices(superSimplex->getSubSimplices(), _sourceSimplex->getId());
}
//-----------------------------------------------------------------------------
void dylab::SimplicalComplex::AdjacentSimplices::mergeAdjacentSubSimplices(Simplex * _sourceSimplex)
{ 
    // check the argument
    DYLAB_ASSERT(_sourceSimplex != NULL);                        

    // check the _order of the source simplex
    if (_sourceSimplex->getOrder() == Simplex::ORDER_0)
        throw EInvalidArgument(String::fmt("Can't merge adjacent sub-simplex map from 0-simplex (id:%1%).", 
			_sourceSimplex->getId()));
    DYLAB_ASSERT(_sourceSimplex->getOrder()-1 == mOrder);

    // merge all sub-simplices of _sourceSimplex
    mergeSimplices(_sourceSimplex->getSubSimplices());
}
//-----------------------------------------------------------------------------
void dylab::SimplicalComplex::AdjacentSimplices::mergeAdjacentSuperSimplices(Simplex * _sourceSimplex)
{
    // check the argument
    DYLAB_ASSERT(_sourceSimplex != NULL);                        

    // check the _order of the source _simplex
    if (_sourceSimplex->getOrder() == Simplex::MAX_ORDER)
        throw EInvalidArgument(String::fmt("Can't merge adjacent super-simplex map from 3-simplex (id:%1%).", 
			_sourceSimplex->getId()));
    DYLAB_ASSERT(_sourceSimplex->getOrder()+1 == mOrder);

    // merge all super-simplices of _sourceSimplex
    mergeSimplices(_sourceSimplex->getSuperSimplices());
}


//===========================================================================================
// SimplicalComplex
//===========================================================================================
dylab::SimplicalComplex::SimplicalComplex()
{
}
//-----------------------------------------------------------------------------
dylab::SimplicalComplex::~SimplicalComplex()
{
    
    for (Simplex::OrderIterator _order; _order.isValid(); _order++)    
    {
        // remove all listeners - they are not destroyed!
        mSimplexListeners[_order].removeAll();    

        // destroy all simplices
        mSimplices[_order].removeAll();
    }
}
//-----------------------------------------------------------------------------
dylab::SimplicalComplex::Simplex::id_t dylab::SimplicalComplex::generateSimplexID(Simplex::order_t _order)
{    
    // create simplices reader
    Simplices::Reader simplicesReader(mSimplices[_order]);

    // maximum number of tries is set to the size of the map of the given _order
    for (uint32_t i = 0; i < simplicesReader.getCount(); i++)
    {
        Simplex::id_t id = mIdGenerator.generate(1);
        if (!simplicesReader.has(id))
            return id;
    }

    // no free id was found so far
    if (true)
        throw EOperationFailed(String::fmt("Unable to generate id for simplex of _order %1%.", _order));

    // unreachable code
    return 0;
}
//-----------------------------------------------------------------------------
void dylab::SimplicalComplex::destroySimplex(Simplex::order_t _order, Simplex::id_t _simplexId)
{ 
    // retrieve the _simplex object
    Simplex * _simplex = mSimplices[_order].get(_simplexId);

    // notify that _order _simplex listeners about the removed _simplex
    for (SimplexListeners::Iterator listener(mSimplexListeners[_order]); listener.isValid(); listener++)
        listener->onSimplexRemoved(_simplex);

    // remove the _simplex from the map
    mSimplices[_order].remove(_simplexId);
}
//-----------------------------------------------------------------------------
void dylab::SimplicalComplex::_notifySimplexCreated(Simplex * _simplex)
{
    // check the _simplex pointer
    DYLAB_ASSERT(_simplex != NULL);

    // store the _simplex to the map of the same _order
    Simplex::order_t _order = _simplex->getOrder();
    mSimplices[_order].insert(_simplex->getId(), _simplex);

    // notify that _order _simplex listeners about the added _simplex    
    for (SimplexListeners::Iterator listener(mSimplexListeners[_order]); listener.isValid(); listener++)
        listener->onSimplexAdded(_simplex);
}
//-----------------------------------------------------------------------------